package main

import (
	"Dataform"
	"Logger"
	"container/list"
	"encoding/json"
	"net"
	"os"
	"strconv"
	"time"
)

var serverList Dataform.ServerInfos
var serverIndex int
var portToListen string
var connections []Dataform.Connection
var availableDock *list.List
var onlineDock *list.List
var sessionIdToDockId map[int]int
var pingerWheel *time.Ticker
var logicConnection, managerClient net.Conn
var logicChannel chan []byte
var distributeChannel chan Dataform.Command

func setLogger() {
	Logger.SetConsole(true)
	Logger.SetRollingDaily("../logs", "Connector-logs.txt")
}

func readConfigFile() {
	serverConfig, err := os.Open("../serverconfigs.conf")
	checkError(err, true)
	defer serverConfig.Close()
	buffer := make([]byte, 2048)
	length, err := serverConfig.Read(buffer)
	checkError(err, true)
	err = json.Unmarshal(buffer[:length], &serverList)
	checkError(err, true)
	Logger.Info("Config file read success...setting up server...")
}

func waitForServerID() {
	var ip, port string
	for i := 0; i < len(serverList.Info); i++ {
		if serverList.Info[i].Type == Dataform.MANAGER {
			ip = serverList.Info[i].Ip
			port = serverList.Info[i].Port
			break
		}
	}
	Logger.Debug("Manager Server is " + ip + ":" + port)
	managerClient, err := net.Dial("tcp", ip+":"+port)
	//will not close when release
	defer managerClient.Close()
	//*****
	checkError(err, false)

	managerClient.Write([]byte("CONNECTOR"))

	buffer := make([]byte, 128)
	length, err := managerClient.Read(buffer)
	checkError(err, true)

	var myServerInfo Dataform.ServerInfo
	err = json.Unmarshal(buffer[:length], &myServerInfo)
	checkError(err, true)

	serverIndex = myServerInfo.Index
	portToListen = myServerInfo.Port
	Logger.Info("Setup success : Server Index:" + strconv.Itoa(serverIndex) + " listening port" + ":" + portToListen)
}

func waitForLogicServer() {
	tlis, err := net.Listen("tcp", ":"+portToListen)
	checkError(err, true)
	defer tlis.Close()
	buffer := make([]byte, 128)
	for {
		tcon, err := tlis.Accept()
		checkError(err, false)
		for {
			length, err := tcon.Read(buffer)
			checkError(err, false)
			if string(buffer[:length]) == "LOGIC" {
				logicConnection = tcon
				Logger.Info("Logic server is online")
				go handleLogicserver()
				return
			}
		}
	}

}

func connectionStop(ind *list.Element) {
	index := ind.Value.(int)
	onlineDock.Remove(ind)
	connections[index].Conn.Close()
	availableDock.PushBack(index)
}

func messageReader(conn *net.Conn, ind *list.Element) {
	index := ind.Value.(int)
	c := *conn

	buffer := make([]byte, 256)

	for {
		length, err := c.Read(buffer)
		if checkError(err, false) {
			connectionStop(ind)
			Logger.Info("Connection " + strconv.Itoa(index) + " is off.")
			return
		}
		Logger.Debug(string(buffer[:length]))
		connections[index].ReadChannel <- buffer[:length]
	}
}

func messageHandler(conn *net.Conn, ind *list.Element) {
	index := ind.Value.(int)
	c := *conn
	buffer := make([]byte, 256)

	go messageReader(conn, ind)

	for {
		select {
		case buffer = <-connections[index].MsgChannel:
			{
				c.Write(buffer)
			}
		case buffer = <-connections[index].ReadChannel:
			{
				if string(buffer) != "PONG" {
					var cmd Dataform.Command
					cmd.SSids = make([]int, 1)
					cmd.SSids[0] = index
					cmd.Content = buffer
					buffer, err := json.Marshal(cmd)
					checkError(err, false)

					logicChannel <- buffer
					Logger.Debug(string(buffer))
				}
				connections[index].PingerChannel <- false
			}
		}
	}
}

func handleConnection(conn *net.Conn, ind *list.Element) {
	index := ind.Value.(int)
	var last, now bool

	go messageHandler(conn, ind)

	for {
		now = <-connections[index].PingerChannel

		if last == true && now == true {
			connectionStop(ind)
			return
		} else if now == true {
			connections[index].MsgChannel <- []byte("PING")
		}
		last = now
	}
}

func handlePinger() {
	for {
		select {
		case <-pingerWheel.C:
			{
				for i := onlineDock.Front(); i != nil; i = i.Next() {
					connections[i.Value.(int)].PingerChannel <- true
				}
			}
		}
	}
}

func distributeMsg(cmd Dataform.Command) {
	for i := 0; i < len(cmd.SSids); i++ {
		connections[cmd.SSids[i]].MsgChannel <- cmd.Content
	}
}

func readFromLogic() {
	buffer := make([]byte, 256)
	var cmd Dataform.Command
	//cmd.SSids = make([]int, Dataform.MaxConnection)
	for {
		lenght, err := logicConnection.Read(buffer)
		checkError(err, false)
		err = json.Unmarshal(buffer[:lenght], &cmd)

		checkError(err, false)
		Logger.Debug(string(buffer[:lenght]))
		go distributeMsg(cmd)
	}
}

func handleLogicserver() {
	logicChannel = make(chan []byte)
	go readFromLogic()
	buffer := make([]byte, 256)
	Logger.Debug("start handle logic server")
	for {
		buffer = <-logicChannel
		Logger.Debug("write to logic")
		logicConnection.Write(buffer)
	}
}

func listenClients() {
	logicChannel = make(chan []byte, 100000)
	pingerWheel = time.NewTicker(time.Second * 15)

	sessionIdToDockId = make(map[int]int)
	connections = make([]Dataform.Connection, 100000)
	availableDock = list.New()
	onlineDock = list.New()
	onlineDock.Init()
	availableDock.Init()

	for i := 0; i < Dataform.MaxConnection; i++ {
		availableDock.PushBack(i)
	}

	go handlePinger()

	listener, err := net.Listen("tcp", ":"+portToListen)
	defer listener.Close()
	checkError(err, true)

	for {
		conn, err := listener.Accept()

		checkError(err, false)
		connections[availableDock.Front().Value.(int)].Conn = conn
		Logger.Info("New connection arrived, storaging in dock " + strconv.Itoa(availableDock.Front().Value.(int)))

		connections[availableDock.Front().Value.(int)].MsgChannel = make(chan []byte, 1)
		connections[availableDock.Front().Value.(int)].PingerChannel = make(chan bool)
		connections[availableDock.Front().Value.(int)].ReadChannel = make(chan []byte)

		go handleConnection(&connections[availableDock.Front().Value.(int)].Conn, availableDock.Front())
		onlineDock.PushBack(availableDock.Front().Value.(int))
		availableDock.Remove(availableDock.Front())
	}
}

func main() {
	setLogger()
	readConfigFile()
	waitForServerID()
	waitForLogicServer()
	listenClients()
}

func checkError(e error, crash bool) bool {
	if e != nil {
		Logger.Error(e.Error())
		if crash {
			os.Exit(444)
		}
		return true
	}
	return false
}
